// 2.3复合类型：基于其他类型定义的类型
//引用和指针
#include <iostream>

int main()
{
    // 2.3.1 引用
    //引用（reference）为对象起了另外一个名字，引用类型引用（refers to）另外一种类型。
    //  通过将声明符写成&d的形式来定义引用类型，其中d是声明的变量名：
    // int ival = 1024;
    // int &refVal = ival; // refVal指向ival(是ival的另一个名字)   别名
    // int &refVal12;      //报错：引用必须被初始化  error: 'refVal12' declared as reference but not initialized

    //引用与它的初始值绑定在一起，而不是将初始值拷贝给引用
    //一旦初始化完成，引用将一直与它的初始值对象一直绑定在一起，
    //无法引用重新绑定到另一个对象，因此引用必须初始化

    //引用即别名
    //引用并非对象，相反的，它只是为一个已经存在的对象所起的另外一个名字
    // int ival = 1024;
    // int &refVal = ival;
    // refVal = 2;
    // int li = refVal;
    // std::cout << ival << std::endl; // ival被赋值为2

    //因为引用不是一个对象，所以不能定义引用的引用

    // 引用的定义
    //允许在一条语句中定义多个引用，其中每个引用标识符都必须以符号&开头：
    // int i = 1024, i2 = 2048; // i和i2都是int
    // int &r = i, r2 = i2;     // r是一个引用，与i绑定在一起，r2是int
    // int i3 = 1024, &ri = i3; // i3是int，ri是一条引用，与i3绑定在一起
    // int &r3 = i3, &r4 = i2;  // r3，r4都是引用

    //引用只能绑定在对象上，而不能与字面值或某个表达式的计算结果绑定在一起
    // int &refVal = 10; //错误，引用类型的初始值必须是一个对象
    // double dval = 3.14;
    // int &refVal5 = dval; //错误：此处引用类型的初始值必须是int型对象
    //                      //引用的数据类型要与引用的初始值的数据类型相一致

    // 2.3.1节练习
    //  2.3.1节练习练习2.15：下面的哪个定义是不合法的？为什么？
    // int ival = 1.01;   //丢失小数部分
    // int &rval1 = 1.01; //不合法，引用类型的初始值不是一个对象
    // int &rval2 = ival; //合法
    // int &rval3;        //不合法，引用必须被初始化

    //练习2.16：考查下面的所有赋值然后回答：哪些赋值是不合法的？为什么？哪些赋值是合法的？它们执行了什么样的操作？
    // int i = 0, &r1 = i;
    // double d = 0, &r2 = d;
    // r2 = 3.14159; //合法，r2与d绑定，d被赋值为3.14159
    // r2 = r1;      //(b)是合法的，以引用作为初始值实际上是以引用绑定的对象作为初始值，在这里是把i的值赋给了变量d
    // i = r2;       //合法被赋予r2的值
    // r1 = d;       //合法

    //【解答】(a)是合法的，为引用赋值实际上是把值赋给了与引用绑定的对象，在这里是把3.14159赋给了变量d。
    // (b)是合法的，以引用作为初始值实际上是以引用绑定的对象作为初始值，在这里是把i的值赋给了变量d。
    // (c)是合法的，把d的值赋给了变量i，因为d是双精度浮点数而i是整数，所以该语句实际上执行了窄化操作。
    // (d)是合法的，把d的值赋给了变量i，与上一条语句一样执行了窄化操作。

    //练习2.17：执行下面的代码段将输出什么结果？
    // int i, &ri = i;
    // i = 5;
    // ri = 10;
    // std::cout << i << " " << ri << std::endl;//10 10

    // 2.3.2指针
    //指针是“指向”另外一种类型的复合类型

    //获取对象的地址
    //指针存放某个对象的地址，要想获取该地址，需要使用取地址符(操作符&);
    // int ival = 42;
    // int *p = &ival; // p存放变量ival的地址，或者说p是指向变量ival的指针
    //第二条语句把p定义为一个指向int的指针，随后初始化p令其指向名为ival的int对象。因为引用不是对象，没有实际
    //地址，所以不能定义指向引用的指针
    //需要跟它所指向的对象严格匹配

    //指针值
    //指针的值（即地址）应属下列4种状态之一：
    // 1.指向一个对象
    // 2.指向紧邻对象所占空间的下一个位置
    // 3。空指针，意味着指针没有指向任何对象
    // 4.无效指针，也就是上述情况之外的其他值    //试图拷贝或以其他方式访问无效指针的值都将引发错误

    //利用指针访问对象
    //如果指针指向了一个对象，则允许使用解引用符（操作符*）来访问该对象：
    // int ival = 42;
    // int *p = &ival;               // p存放着变量ival的地址，或者说p是指向变量ival的指针
    // std::cout << *p << std::endl; //由符号*得到指针p所指的对象，输出42

    //对指针解引用会得出所指的对象，因此如果给解引用的结果赋值，实际上也就是给指针所指的对象赋值
    // *p = 0;                       //由符号*得到指针p所指的对象，即可经由p为变量ival赋值
    // std::cout << *p << std::endl; //输出0
    // note:解引用操作仅适用于那些确实指向了某个对象的有效指针

    //空指针
    //空指针不指向任何对象
    // //生成空指针的方法：
    // int *p1 = nullptr; //等价于int *p1=0;          //c11新特性
    // int *p2 = 0;      //直接将p2初始化为字面常量0
    // //需要首先#include cstdlib
    // int *p3 = NULL; //等价于int *p3=0;

    // //建议：初始化所有指针

    //其他指针的操作
    //只要指针拥有一个合法指，就能将它用在条件表达式种。
    // 如果指针的值是0，条件取false；任何非0指针对应的条件值都是true
    // int ival = 1024;
    // int *pi = 0;
    // int *pi2 = &ival;
    // if (pi)
    // { //输出假       //*pi==0时为假则判断语句不输出
    //     printf("真\n");
    // }
    // else
    // {
    //     std::cout << "假" << std::endl;
    // }
    // if (pi2)
    // { //输出真
    //     std::cout << "真" << std::endl;
    // }
    // else
    // {
    //     std::cout << "假" << std::endl;
    // }

    // void* 指针
    //可以存放任何对象的地址
    //概括来说，以void*的视角来看内存空间也就仅仅是内存空间，没办法访问内存空间中所存的对象

    // 2.3.2节练习
    //  练习2.18：编写代码分别更改指针的值以及指针所指对象的值。
    //更改指针的值(地址),就是改变指针所指向的对象
    // int k=0,k1=0  ;
    // int *p =&k;
    // std::cout << p << std::endl;//0x61fe14
    // p=&k1; //不能把int变量直接赋值给指针
    // std::cout << p<<std::endl;//0x61fe10
    // //更改指针所指对象的值
    // int ival = 100;
    // p = &ival;
    // *p = 255;
    // std::cout << ival<< std::endl;//255

    //练习2.19：说明指针和引用的主要区别
    //引用是跟对象捆绑，定义的时候必须要有对象
    //指针是指向对象的地址，一个程序内可以指向多个对象的地址
    // 指针“指向”内存中的某个对象，而引用“绑定到”内存中的某个对象，它们都实现了对其他对象的间接访问，二者的区别主要有两方面：
    // 第一，指针本身就是一个对象，允许对指针赋值和拷贝，而且在指针的生命周期内它可以指向几个不同的对象；引用不是一个对象，无法令引用重新绑定到另外一个对象。
    // 第二，指针无须在定义时赋初值，和其他内置类型一样，在块作用域内定义的指针如果没有被初始化，也将拥有一个不确定的值；引用则必须在定义时赋初值。
    // 引用即别名，指针更像是地址门牌

    // 练习2.20：请叙述下面这段代码的作用。
    // int i = 42;//声明定义一个i，i的值是42
    // int *p1 = &i;//p1存放着i的地址，或者说p1是指向i的指针
    // *p1 = *p1 * *p1;//i=i*i

    // 练习2.21：请解释下述定义。在这些定义中有非法的吗？如果有，为什么？
    // int i = 0;
    // double *dp = &i;//非法，指针的类型与dp的类型不一致
    // int *ip = i;//非法，不能将int类型的值直接赋值给指针
    // int *p = &i;//合法

    // 练习2.22：假设p是一个int型指针，请说明下述代码的含义。
    // if(p)p的值为0(地址)时为假不执行条件，就是p是空指针时不执行条件
    // if(*p)*p的值为0的时候为假不执行条件

    //练习2.23：给定指针p，你能知道它是否指向了一个合法的对象吗？如果能，叙述判断的思路；如果不能，说明原因。
    //打印输出

    //在C++程序中，应该尽量初始化所有指针，并且尽可能等定义了对象之后再定义指向它的指针。
    // 如果实在不清楚指针应该指向何处，就把它初始化为nullptr或者0，这样程序就能检测并知道它有没有指向一个具体的对象了。
    // 其中，nullptr是C++11新标准刚刚引入的一个特殊字面值，它可以转换成任意其他的指针类型。
    // 在此前提下，判断p是否指向合法的对象，只需把p作为if语句的条件即可，如果p的值是nullptr，则条件为假；反之，条件为真。
    // 如果不注意初始化所有指针而贸然判断指针的值，则有可能引发不可预知的结果。
    // 一种处理的办法是把if(p)置于try结构中，当程序块顺利执行时，表示p指向了合法的对象；当程序块出错跳转到catch语句时，表示p没有指向合法的对象。

    //练习2.24：在下面这段代码中为什么p合法而lp非法？
    // int i = 42;
    // void *p = &i;
    // long *lp = &i;
    // p合法是因为void*指针可以存放任意对象的地址
    // lp不合法是因为指针的类型与对象的类型不一致

    // 2.3.3理解复合类型的声明
    //  变量的定义包括一个基本数据类型和一组声明符

    //在同一条定义语句中，虽然基本数据类型只有一个，但是声明符的形式却可以不同，也就是说，一条定义语句可能定义出不同类型的变量
    int i = 1024, *p = &i, &r = i; // i是一个int类型的数，p是一个int型指针，r是一个int型引用

    // 指向指针的指针

    //指向指针的引用
    // //引用本身不是一个对象，因此不能定义指向引用的指针，但指针是对象，所以存在对指针的引用
    // int i = 42;
    // int *p;      // p是一个int型指针
    // int *&r = p; // r是一个对指针p的引用

    // r = &i; // r引用一个指针，因此给r赋值&i就是令p指向i
    // *r = 0; //解引用r得到i，也就是p指向的对象，将i的值改为0
    //要理解r的类型到底是什么，最简单的方法就是从右向左阅读r的定义

    // 2.3.3节练习
    //练习2.25：说明下列变量的类型和值。
    int *ip, i, &r = i; // ip是int类型的指针，i是int类型的整数，r是一个引用，它绑定了i，可以看作是i的别名，r的值就是i的值。
    int i, *ip = 0;     // i是int类型的整数，ip是int类型的指针，是空指针
    int *ip, ip2;       // ip是int类型的指针，ip2整型数
    // 【解答】
    // (a)ip是一个整型指针，指向一个整型数，它的值是所指整型数在内存中的地址；i是一个整型数；r是一个引用，它绑定了i，可以看作是i的别名，r的值就是i的值。
    // (b)i是一个整型数；ip是一个整型指针，但是它不指向任何具体的对象，它的值被初始化为0。
    // (c)ip是一个整型指针，指向一个整型数，它的值是所指整型数在内存中的地址；ip2是一个整型数。

    return 0;
}
